﻿using System;
using System.IO;
using System.Runtime.InteropServices;

namespace pyrale
{
    public struct Position
    {
        public int X;
        public int Y;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct LevelDataRow
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 
        public byte[] Cells;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct LevelData
    {
        public byte Always1; //1 byte
        public byte Background; //1 byte
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xA)] //0xA0 bytes
        public LevelDataRow[] Rows; 
        public uint ParTime; //4 bytes 
        public uint Crc32; //4 bytes
    } //over all 0xAA bytes

    public class Pyramids
    {
        public const int LevelHeight = 10;
        public const int LevelWidth = 16;

        private LevelData _levelData;

        private static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
        {
            var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
            var temp = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
            handle.Free();
            return temp;
        }

        private static byte[] StructureToByteArray<T>(T structure) where T : struct
        {
            var size = Marshal.SizeOf(structure);
            var byteArray = new byte[size];
            var pointer = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(structure, pointer, false);
            Marshal.Copy(pointer, byteArray, 0, size);
            Marshal.FreeHGlobal(pointer);
            return byteArray;
        }

        public void New()
        {
            _levelData.Always1 = 1;
            _levelData.Background = 0;
            _levelData.ParTime = 30;
            _levelData.Rows = new LevelDataRow[LevelHeight];
            for (var i = 0; i < _levelData.Rows.Length; i++)
                _levelData.Rows[i] = new LevelDataRow {Cells = new byte[LevelWidth]};
            _levelData.Crc32 = 0;
        }

        public void Read(Stream stream)
        {
            var decompressed = new byte[Marshal.SizeOf(typeof(LevelData))];
            var ms = new MemoryStream(decompressed);
            var lz10 = new DSDecmp.Formats.Nitro.LZ10();
            try
            {
                lz10.Decompress(stream, stream.Length, ms);
            }
            catch//(Exception ex)
            {}
            ms.Close();
            _levelData = ByteArrayToStructure<LevelData>(decompressed);
        }

        public void Write(Stream stream)
        {
            //calculate crc32
            var decompressed = StructureToByteArray(_levelData);
            var crc = Crc32.Calculate(decompressed, 0, decompressed.Length - 4);
            Buffer.BlockCopy(BitConverter.GetBytes(crc), 0, decompressed, decompressed.Length - 4, 4);
            var lz10 = new DSDecmp.Formats.Nitro.LZ10();
            var ms = new MemoryStream(decompressed);
            lz10.Compress(ms, decompressed.Length, stream);
        }

        public byte Get(int x, int y)
        {
            return _levelData.Rows[y].Cells[x];
        }

        public void Set(int x, int y, byte b)
        {
            _levelData.Rows[y].Cells[x] = b;
        }

        public byte GetBackground()
        {
            return _levelData.Background;
        }

        public void SetBackground(byte background)
        {
            _levelData.Background = background;
        }

        public uint GetParTime()
        {
            return _levelData.ParTime;
        }

        public void SetParTime(uint parTime)
        {
            _levelData.ParTime = parTime;
        }

        public static bool IsSand(byte b)
        {
            return (b == 0x03 || b == 0x15 || b == 0x17 || b == 0x1A || b == 0x1C || b == 0x1E || b==0x20 || b == 0x22 || b==0x01);
        }
    }
}
